/*
 * Copyright (c) 2015 elion All Rights Reserved.
 */
package com.guoyu.project.common.mybatis;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

/**
 * mybatis基础的枚举转换器
 * 
 * @author: xblibo Date: 2015/5/17 Time: 13:50
 */
public abstract class BaseEnumTypeHandler<T extends Enum> implements TypeHandler<T> {
    /**
     * 泛型类型
     */
    private Class<Enum> entityClass;
    /**
     * 泛型枚举中的getCode方法
     */
    private Method codeMethod;
    /**
     * code 与 枚举的映射
     */
    private Map<Integer, Enum> codeMap;

    public BaseEnumTypeHandler() {
        // 获取子类中的泛型类型
        entityClass = (Class<Enum>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        try {
            codeMethod = entityClass.getDeclaredMethod("getCode");
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("not found code method in enum : " + entityClass, e);
        } catch (SecurityException e) {
            throw new IllegalArgumentException(e);
        }

        try {
            Method valuesMethod = entityClass.getDeclaredMethod("values");
            Enum[] enums = (Enum[]) valuesMethod.invoke(null);
            codeMap = new HashMap<Integer, Enum>();
            for (Enum e : enums) {
                codeMap.put((Integer) invokeCode(e), e);
            }
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 从数据库中查询出来的列转换成枚举
     * 
     * @param rs
     * @param columnName
     * @return
     * @throws SQLException
     */
    @Override
    public T getResult(ResultSet rs, String columnName) throws SQLException {
        return invokeCodeOf(rs.getInt(columnName));
    }

    /**
     * 从数据库中查询出来的列转换成枚举
     *
     * @param rs
     * @param columnIndex
     * @return
     * @throws SQLException
     */
    @Override
    public T getResult(ResultSet rs, int columnIndex) throws SQLException {
        return invokeCodeOf(rs.getInt(columnIndex));
    }

    /**
     * 从数据库中查询出来的列转换成枚举
     *
     * @param cs
     * @param columnIndex
     * @return
     * @throws SQLException
     */
    @Override
    public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
        return invokeCodeOf(cs.getInt(columnIndex));
    }

    /**
     * 把枚举转换成数据库表中的列类型
     *
     * @param stmt
     * @param columnIndex
     * @param param
     * @param jdbcType
     * @throws SQLException
     */
    @Override
    public void setParameter(PreparedStatement stmt, int columnIndex, T param, JdbcType jdbcType) throws SQLException {
        if (jdbcType == null) {
            stmt.setString(columnIndex, String.valueOf(invokeCode(param)));
        } else {
            stmt.setObject(columnIndex, invokeCode(param), jdbcType.TYPE_CODE);
        }
    }

    /**
     * code转换成枚举
     * 
     * @param code
     * @return
     */
    private T invokeCodeOf(Integer code) {
        Enum e = codeMap.get(code);
        if (e == null) {
            throw new IllegalArgumentException("not found code " + code + " in enum " + entityClass);
        }
        return (T) e;
    }

    /**
     * 枚举转成code
     * 
     * @param o
     * @return
     */
    private Integer invokeCode(Object o) {
        try {
            return (Integer) codeMethod.invoke(o);
        } catch (Exception e) {
            throw new RuntimeException("invoke code method error!", e);
        }
    }

}
